package org.sfans.admin.controller.api;

import static org.sfans.admin.utils.WebUtil.updatePageable;

import javax.validation.Valid;

import org.sfans.admin.bind.CaseInsentiveEnumEditor;
import org.sfans.admin.domain.UserForm;
import org.sfans.admin.utils.ModelMapper;
import org.sfans.core.domain.User;
import org.sfans.core.domain.UserRepository;
import org.sfans.core.web.exception.InputValidationException;
import org.sfans.core.web.exception.ResourceConflictException;
import org.sfans.core.web.exception.ResourceNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Profile("admin")
@RestController
@RequestMapping("${sfans.admin.urls.api}/users")
@Secured("ROLE_ADMIN")
public class UsersController {

	private static final Logger LOG = LoggerFactory.getLogger(UsersController.class);

	@Autowired
	private UserRepository repository;

	@Autowired
	private ModelMapper mapper;

	@Autowired
	private PasswordEncoder encoder;

	@Value("${sfans.admin.domain.user.items-per-page:20}")
	private int itemsPerPage;

	public static enum UpdateAction {
		LOCK, UNLOCK, DISABLE, ENABLE
	}

	public static enum UserFilterByType {
		ALL, ENABLED, DISABLED, LOCKED, NONLOCKED, EXPIRED, NONEXPIRED, CREDENTIALS_EXPIRED, CREDENTIALS_NONEXPIRED
	}

	@InitBinder
	public void init(final WebDataBinder binder) {
		binder.registerCustomEditor(UserFilterByType.class,
				new CaseInsentiveEnumEditor<>(UserFilterByType.class));
		binder.registerCustomEditor(UpdateAction.class,
				new CaseInsentiveEnumEditor<>(UpdateAction.class));
	}

	@RequestMapping("/available/{username}")
	@Transactional(readOnly = true)
	public void findByIdentifier(@PathVariable("username") final String username) {
		if (!repository.findByUsername(username).isPresent()) {
			throw new ResourceNotFoundException(String.format("%s 不是一个有效的用户", username));
		}
	}

	@RequestMapping(method = RequestMethod.GET)
	@Transactional(readOnly = true)
	public Page<User> searchusersByType(@PageableDefault(size = 10) final Pageable pageInfo,
			@RequestParam(value = "filter", required = false, defaultValue = "ALL") final UserFilterByType type) {

		final Pageable pageable = updatePageable(pageInfo, itemsPerPage);

		switch (type) {
		case ENABLED:
			return repository.findByAccountDisabled(false, pageable);
		case DISABLED:
			return repository.findByAccountDisabled(true, pageable);
		case LOCKED:
			return repository.findByAccountLocked(true, pageable);
		case NONLOCKED:
			return repository.findByAccountLocked(false, pageable);
		case EXPIRED:
			return repository.findByAccountExpired(pageable);
		case NONEXPIRED:
			return repository.findByAccountNonExpired(pageable);
		case CREDENTIALS_EXPIRED:
			return repository.findByCredentialsExpired(pageable);
		case CREDENTIALS_NONEXPIRED:
			return repository.findByCredentialsNonExpired(pageable);
		default:
			return repository.findAll(pageable);
		}
	}

	@RequestMapping(method = RequestMethod.POST)
	@Transactional
	public User createUser(@RequestBody @Valid final UserForm form, final BindingResult result) {

		LOG.debug("User creation :: {} (has errors: {})", form, result.hasErrors());

		verifyUserForm(form, result);

		User user = mapper.from(form, User.class);

		user.getAccount().setUsername(form.getUsername());
		user.getAccount().setPassword(encoder.encode(form.getPassword()));
		user.getAccount().addAuthorities(form.getRolesAsString());

		return repository.save(user);
	}

	private void verifyUserForm(final UserForm form, final BindingResult result) {
		if (result.hasErrors()) {
			throw new InputValidationException();
		}

		if (repository.findByUsername(form.getUsername()).isPresent()) {
			throw new InputValidationException(String.format("用户名 [%s] 已经存在了", form.getUsername()));
		}
	}

	@RequestMapping(value = "/{user_id}/action", method = RequestMethod.PATCH)
	@Transactional
	public void updateUserAvailability(@PathVariable("user_id") final Long id,
			@PathVariable("action") final UpdateAction action,
			@AuthenticationPrincipal final User currentUser) {

		LOG.debug("User update :: action:[{}],target:[id:{}],current:{}", action, id, currentUser);

		final User user = lookupUser(id);

		switch (action) {
		case LOCK:
			lockUser(user);
			break;
		case UNLOCK:
			unlockUser(user);
			break;
		case DISABLE:
			disableUser(user);
			break;
		case ENABLE:
			enableUser(user);
			break;
		}

	}

	@RequestMapping(value = "/{user_id}", method = RequestMethod.DELETE)
	@Transactional
	public void deleteUser(@PathVariable("user_id") final Long id,
			@AuthenticationPrincipal final User currentUser) {
		LOG.debug("Deleting user :: id:[{}], current:{}", id, currentUser);

		final User user = lookupUser(id);
		if (user.getId() == currentUser.getId()) {
			throw new ResourceConflictException("用户不能删除自己的账号！");
		}

		repository.delete(user);
	}

	private void enableUser(final User user) {
		if (!user.getAccount().isDisabled()) {
			throw new ResourceConflictException(String.format("%s 已经是有效用户", user.getUsername()));
		}
		user.getAccount().setDisabled(false);
	}

	private void disableUser(final User user) {
		if (user.getAccount().isDisabled()) {
			throw new ResourceConflictException(String.format("%s 已经是无效用户", user.getUsername()));
		}
		user.getAccount().setDisabled(true);
	}

	private void unlockUser(final User user) {
		if (!user.getAccount().isLocked()) {
			throw new ResourceConflictException(String.format("%s 用户没有被锁定", user.getUsername()));
		}
		user.getAccount().setLocked(false);
	}

	private void lockUser(final User user) {
		if (user.getAccount().isLocked()) {
			throw new ResourceConflictException(String.format("%s 用户已经被锁定", user.getUsername()));
		}
		user.getAccount().setLocked(true);
	}

	private User lookupUser(final Long id) {

		if (repository.exists(id)) {
			throw new ResourceNotFoundException();
		}
		return repository.findOne(id);
	}

}
